/*
 * Created by SharpDevelop.
 * User: narmi79
 * Date: 7/19/2007
 * Time: 11:35 AM
 */

using System;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using System.Collections.Generic;

namespace Geotag
{
	/// <summary>
	/// TShapeType enum contains definition for shape types
	/// </summary>
	public enum TShapeType
	{
		NullShape	= 0,

  		// 2D Shape Types (pre ArcView 3.x)
  		Point		= 1,	// Points
  		PolyLine	= 3,	// Arcs (Polylines, possible in parts)
  		Polygon		= 5,	// Polygons (possible in parts)
  		MultiPoint	= 8,	// MultiPoint (related points)

		// 3D Shape Types (may include "measure" values for vertices)
		PointZ		= 11,
  		PolyLineZ		= 13,
  		PolygonZ	= 15,
  		MultiPointZ	= 18,

		// 2D + Measure Types
		PointM		= 21,
		PolyLineM		= 23,
		PolygonM	= 25,
		MultiPointM	= 28,

		// Complex (TIN-like) with Z, and Measure
		MultiPatch	= 31	
	};
	
	/// <summary>
	/// TPartType enum contains definition for part types
	/// </summary>
	public enum TPartType
	{
		TriangleStrip	= 0,
		TriangleFan		= 1,
		OuterRing		= 2,
		InnerRing		= 3,
		FirstRing		= 4,
		Ring			= 5
	};
	
	/// <summary>
	/// ShapefileInfo class contains Shapefile header info
	/// </summary>
	public class ShapefileInfo
	{
		public const int FileCode = 9994;	// at byte 0
		public long FileLength;				// at byte 24
		public int Version;					// at byte 28
		public TShapeType ShapeType;		// at byte 32
		public double XMin;					// at byte 36
		public double YMin;					// at byte 44
		public double XMax; 				// at byte 52
		public double YMax; 				// at byte 60
		public double ZMin; 				// at byte 68
		public double ZMax;					// at byte 76
		public double MMin; 				// at byte 84
		public double MMax; 				// at byte 92
		
		public override string ToString()
		{
			string str = "";
			str += "ShapefileInfo.FileCode:   " + FileCode.ToString() + "\r\n";
			str += "ShapefileInfo.FileLength: " + FileLength.ToString() + " Bytes\r\n";
			str += "ShapefileInfo.Version:    " + Version.ToString() + "\r\n";
			str += "ShapefileInfo.ShapeType:  " + System.Enum.GetName(typeof(TShapeType), ShapeType) + "\r\n";
			str += "ShapefileInfo.X:          min(" + XMin.ToString() + ") max(" + XMax.ToString() + ")" + "\r\n";
			str += "ShapefileInfo.Y:          min(" + YMin.ToString() + ") max(" + YMax.ToString() + ")" + "\r\n";
			str += "ShapefileInfo.Z:          min(" + ZMin.ToString() + ") max(" + ZMax.ToString() + ")" + "\r\n";
			str += "ShapefileInfo.M:          min(" + MMin.ToString() + ") max(" + MMax.ToString() + ")" + "\r\n";
			return str;
		}
	}
		
	/// <summary>
	/// Shape class contains Shapefile shape methods and operations
	/// </summary>
	public class Shape
	{
		public TShapeType ShapeType;
		public long ContentLength;
		public int ShapeId;						// Shape Number (-1 is unknown/unassigned)
		public int NumParts;					// Number of Parts (0 implies single part with no info)
		public LinkedList<int> Parts;			// Start Vertex of part
		public LinkedList<TPartType> PartTypes;	// Part Type (Ring if not MultiPatch)
    	public int NumPoints;					// Number of Points 
    	public LinkedList<double> X;
    	public LinkedList<double> Y;
    	public LinkedList<double> Z;			// (all zero if not provided)
    	public LinkedList<double> M;			// (all zero if not provided)

		public double XMin;						// Bounds in X, Y, Z and M dimensions
		public double YMin;
		public double ZMin;
		public double MMin;

		public double XMax;
		public double YMax;
		public double ZMax;
		public double MMax;
		
		public Shape()
		{
			ShapeType = TShapeType.NullShape;
			ContentLength = 0;
			ShapeId = 0;
			NumParts = 0;
			Parts = new LinkedList<int>();
			PartTypes = new LinkedList<TPartType>();
			NumPoints = 0;
			X = new LinkedList<double>();
			Y = new LinkedList<double>();
			Z = new LinkedList<double>();
			M = new LinkedList<double>();
			XMin = 0;
			YMin = 0;
			ZMin = 0;
			MMin = 0;
			XMax = 0;
			YMax = 0;
			ZMax = 0;
			MMax = 0;
		}
		
		public override string ToString()
		{
			string str = "";
			str += "Shape.ShapeId:       " + ShapeId.ToString() + "\r\n";
			str += "Shape.ContentLength: " + ContentLength.ToString() + " Bytes, (" + (ContentLength+8).ToString() + " Bytes Record Length)\r\n";
			str += "Shape.ShapeType:     " + System.Enum.GetName(typeof(TShapeType), ShapeType) + "\r\n";
			str += "Shape.BoundingBox:   Xmin(" + XMin.ToString() + ") Ymin(" + YMin.ToString() + ") Xmax(" + XMax.ToString() + ") Ymax(" + YMax.ToString() + ")\r\n";
			str += "Shape.MeasureRange:  Mmin(" + MMin.ToString() + ") Mmax(" + MMax.ToString() + ")\r\n";
			str += "Shape.ZRange:        Zmin(" + ZMin.ToString() + ") Zmax(" + ZMax.ToString() + ")\r\n";
			str += "Shape.NumParts:      " + NumParts.ToString() + "\r\n";
			str += "Shape.NumPoints:     " + NumPoints.ToString() + "\r\n";
			return str;
		}
	}
	
	/// <summary>
	/// ShapefileReader reads Shapefile and builds a linked ListOfShapes
	/// </summary>
	public class ShapefileReader
	{
		private const int bufferSize = 512;
		private FileStream fs;
		private BinaryReader br;
		public ShapefileInfo ShapefileInfo;
		
		public ShapefileReader(string shapefile, LinkedList<Shape> listOfShapes)
		{
			try
			{
				// Open Shapefile stream
				fs = new FileStream(shapefile,
	                                FileMode.Open, 
	                                FileAccess.Read, 
	                                FileShare.None, 
	                                bufferSize, 
	                                FileOptions.SequentialScan
	                               );
				
				// Create binary reader for this file stream
				br = new BinaryReader(fs);
				
				// Create and populate ShpapefileInfo object.
				ShapefileInfo = new ShapefileInfo();
				
				// Read and verify file code
				int temp = EndianFlipInt32(br.ReadInt32());
				if (temp != ShapefileInfo.FileCode)
				{
					throw new FileNotFoundException();
				}
				
				// Read and discard next five unused Int32 fields
				for (int i=0; i<5; i++)
					temp = EndianFlipInt32(br.ReadInt32());
				
				// Read and store file length
				ShapefileInfo.FileLength = EndianFlipInt32(br.ReadInt32())*2;
				
				// Read and store version
				ShapefileInfo.Version = br.ReadInt32();
				
				// Read and store shape type
				ShapefileInfo.ShapeType = (TShapeType)br.ReadInt32();
				
				// Read and store XMin
				ShapefileInfo.XMin = br.ReadDouble();
				
				// Read and store YMin
				ShapefileInfo.YMin = br.ReadDouble();
				
				// Read and store XMax
				ShapefileInfo.XMax = br.ReadDouble();
				
				// Read and store YMax
				ShapefileInfo.YMax = br.ReadDouble();
				
				// Read and store ZMin
				ShapefileInfo.ZMin = br.ReadDouble();
				
				// Read and store ZMax
				ShapefileInfo.ZMax = br.ReadDouble();
				
				// Read and store MMin
				ShapefileInfo.MMin = br.ReadDouble();
				
				// Read and store MMax
				ShapefileInfo.MMax = br.ReadDouble();
				
				// Write Debug message
				Debug.WriteLine(ShapefileInfo.ToString());
				
				// Read records, create shapes and add them to the listOfShapes
				while(true)
				{
					Shape shape = new Shape();
					shape.ShapeId = EndianFlipInt32(br.ReadInt32());
					shape.ContentLength = EndianFlipInt32(br.ReadInt32())*2; 
					shape.ShapeType = (TShapeType)br.ReadInt32();
					
					int tempI = 0;
					double tempD = 0;
					switch(shape.ShapeType)
					{
						case TShapeType.NullShape:		
							break;
						
						case TShapeType.Point:			
							tempD = br.ReadDouble();			// X Coordinate
							shape.X.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Y Coordinate
							shape.Y.AddLast(new LinkedListNode<double>(tempD));
							break;
													
						case TShapeType.MultiPoint:		
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
													
						case TShapeType.PolyLine:		
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumParts
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
						
						case TShapeType.Polygon:		
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumRings
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
													
						case TShapeType.PointM:
							tempD = br.ReadDouble();			// X Coordinate
							shape.X.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Y Coordinate
							shape.Y.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Measure
							shape.M.AddLast(new LinkedListNode<double>(tempD));
							break;
						
						case TShapeType.MultiPointM:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
						
						case TShapeType.PolyLineM:		
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumParts
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
							
						case TShapeType.PolygonM:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumRings
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
							
						case TShapeType.PointZ:		
							tempD = br.ReadDouble();			// X Coordinate
							shape.X.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Y Coordinate
							shape.Y.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Z Coordinate
							shape.Z.AddLast(new LinkedListNode<double>(tempD));
							tempD = br.ReadDouble();			// Measure
							shape.M.AddLast(new LinkedListNode<double>(tempD));
							break;
							
						case TShapeType.MultiPointZ:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.ZMin = br.ReadDouble();		// ZMin
							shape.ZMax = br.ReadDouble();		// ZMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Z Coordinate
								shape.Z.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
							
						case TShapeType.PolyLineZ:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumParts
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.ZMin = br.ReadDouble();		// ZMin
							shape.ZMax = br.ReadDouble();		// ZMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Z Coordinate
								shape.Z.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
							
						case TShapeType.PolygonZ:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumRings
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.ZMin = br.ReadDouble();		// ZMin
							shape.ZMax = br.ReadDouble();		// ZMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Z Coordinate
								shape.Z.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
							
						case TShapeType.MultiPatch:
							shape.XMin = br.ReadDouble();		// XMin
							shape.YMin = br.ReadDouble();		// YMin
							shape.XMax = br.ReadDouble();		// XMax
							shape.YMax = br.ReadDouble();		// YMax
							shape.NumParts = br.ReadInt32();	// NumPatches
							shape.NumPoints = br.ReadInt32();	// NumPoints
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PatchStart
								shape.Parts.AddLast(new LinkedListNode<int>(tempI));
							}
							for(int i=0; i<shape.NumParts; i++)
							{
								tempI = br.ReadInt32();			// PartTypes
								TPartType tpt = (TPartType) tempI;
								shape.PartTypes.AddLast(new LinkedListNode<TPartType>(tpt));
							}
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// X Coordinate
								shape.X.AddLast(new LinkedListNode<double>(tempD));
								tempD = br.ReadDouble();		// Y Coordinate
								shape.Y.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.ZMin = br.ReadDouble();		// ZMin
							shape.ZMax = br.ReadDouble();		// ZMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Z Coordinate
								shape.Z.AddLast(new LinkedListNode<double>(tempD));
							}
							shape.MMin = br.ReadDouble();		// MMin
							shape.MMax = br.ReadDouble();		// MMax
							for(int i=0; i<shape.NumPoints; i++)
							{
								tempD = br.ReadDouble();		// Measure
								shape.M.AddLast(new LinkedListNode<double>(tempD));
							}
							break;
					}
					
					LinkedListNode<Shape> node = new LinkedListNode<Shape>(shape);
					listOfShapes.AddLast(node);
					
					// Write Debug message
					Debug.WriteLine(shape.ToString());
				}
			}
			catch(EndOfStreamException ex)
			{
				br.Close();
				fs.Close();
				
				Debug.WriteLine(ex.ToString());
				Debug.WriteLine(listOfShapes.Count.ToString() + " shapes added to ListOfShapes.");
			}
			catch(Exception ex)
			{
				MessageBox.Show(ex.Message, "Geotag", MessageBoxButtons.OK, MessageBoxIcon.Error);
				Debug.WriteLine(ex.ToString());
			}
		}
		
		private void swapBytes(ref byte x, ref byte y)
		{
			byte temp = x;
			x = y;
			y = temp;
		}
		
		private Int32 EndianFlipInt32(Int32 integer)
		{
			byte[] bytes = BitConverter.GetBytes(integer);
			swapBytes(ref bytes[0], ref bytes[3]);
			swapBytes(ref bytes[1], ref bytes[2]);
			return BitConverter.ToInt32(bytes, 0);
		}
		
		private Int16 EndianFlipInt16(Int16 integer)
		{
			byte[] bytes = BitConverter.GetBytes(integer);
			swapBytes(ref bytes[0], ref bytes[1]);
			return BitConverter.ToInt16(bytes, 0);
		}
	}
	
	/// <summary>
	/// ShapefileWriter builds a linked ListOfShapes and writes it to the disk
	/// </summary>
	public class ShapefileWriter
	{
		private ShapefileInfo info;
		private LinkedList<Shape> listOfShapes;
		
		public ShapefileWriter(ShapefileInfo shapefileInfo)
		{
			listOfShapes = new LinkedList<Shape>();
		}
		
		public int AddShape(Shape shape)
		{
			return 0;
		}
		
		public void WriteToDisk(string fileName)
		{
			FileStream fs;
			BinaryWriter bw;
		}
	}
}
